/*
 * @(#)TriggerParser.java
 *
 * Copyright (c) 2003 DCIVision Ltd
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of DCIVision
 * Ltd ("Confidential Information").  You shall not disclose such Confidential
 * Information and shall use it only in accordance with the terms of the license
 * agreement you entered into with DCIVision Ltd.
 */
package com.dcivision.workflow.core;

import java.sql.Timestamp;
import java.text.ParseException;
import java.util.Calendar;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.quartz.CronTrigger;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;

import com.dcivision.framework.GlobalConstant;
import com.dcivision.framework.Utility;
import com.dcivision.workflow.bean.WorkflowSchedule;

/**
 * TriggerParser.java 
 * 
 * This class generate a Quartz Trigger object from a WorkflowSchedule bean.
 * 
 * @author Lun Au
 * @company DCIVision Limited
 * @creation date 23/12/2004
 * @version $Revision: 1.9 $
 * @see com.dcivision.workflow.bean.WorkflowSchedule
 * @see com.dcivision.workflow.bean.WorkflowScheduleTrigger
 */
public class TriggerParser {
  
  public static final String REVISION = "$Revision: 1.9 $";
  
  protected static Log log = LogFactory.getLog("com.dcivision.workflow.core.TriggerParser");
  
  private TriggerParser(){
  }
  
  private static String joinArray ( String s[], String separator ) {
    if ( s == null ) return null;
    if ( s.length == 0 ) return "";
    
    StringBuffer sb = new StringBuffer();
    for ( int i = 0 ; i < s.length ; i++ ) {
      sb.append(s[i]).append(separator);
    }
    
    return sb.substring(0,sb.length()-1);
  }
  
  /**
   * A "Cron-Expression" is a string comprised of 6 or 7 fields separated by white space.
   * The 6 mandatory and 1 optional fields are as follows:
   *
   * Field Name   Allowed Values   Allowed Special Characters
   * Seconds         0-59               , - * /
   * Minutes         0-59               , - * /
   * Hours           0-23               , - * /
   * Day-of-month    1-31               , - * ? / L W C
   * Month           1-12 or JAN-DEC    , - * /
   * Day-of-Week     1-7 or SUN-SAT     , - * ? / L C #
   * Year (Optional) empty, 1970-2099   , - * /
   *
   *
   * @param workflowSchedule
   * @return
   * @throws ParseException
   */
  public static Trigger parse ( WorkflowSchedule workflowSchedule ) throws ParseException {
    //  Cron-Expressions are used to configure instances of CronTrigger.
    //  Cron-Expressions are strings that are actually made up of six
    //  sub-expressions, that describe individual details of the schedule.
    //  These sub-expression are separated with white-space, and represent:
    //  1- Seconds
    //  2- Minutes
    //  3- Hours
    //  4- Day-of-Month
    //  5- Month
    //  6- Day-of-Week
    //  7- Year
    
    String cronExpression = null;
    String cronSeconds = null;
    String cronMinutes = null;
    String cronHours = null;
    String cronDaysOfMonth = null;
    String cronMonths = null;
    String cronDaysOfWeek = null;
    
    int startDay = 0;
    
    cronSeconds = "0";
    cronMinutes = String.valueOf(workflowSchedule.getTriggerMinute());
    cronHours = String.valueOf(workflowSchedule.getTriggerHour());
    
    if ( !Utility.isEmpty(workflowSchedule.getStartDate()) ) {
      Calendar cal = Utility.timestampToCalendar( workflowSchedule.getStartDate() );
      startDay = cal.get(Calendar.DAY_OF_MONTH);
    }
    
    // Schedule Type: Once
    if ( workflowSchedule.getTriggerType() == WorkflowSchedule.TRIGGER_TYPE_ONCE ) {
      // generate the simple trigger object

    	Calendar calendar = Calendar.getInstance();
    	calendar.setTime(workflowSchedule.getStartDate());
/*    	calendar.set(Calendar.HOUR,workflowSchedule.getTriggerHour());
    	calendar.set(Calendar.MINUTE,workflowSchedule.getTriggerMinute());*/
    	
      SimpleTrigger trigger = new SimpleTrigger(GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + workflowSchedule.getID(),
          GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + "GROUP",
          calendar.getTime(), null,0,0);
      return trigger;
      
      // Schedule Type: Daily
    } else if ( workflowSchedule.getTriggerType() == WorkflowSchedule.TRIGGER_TYPE_DAILY ) {
      // Daily: Everyday
      if ( workflowSchedule.getTriggerStyle() == WorkflowSchedule.DAILY_STYLE_EVERYDAY ) {
        cronDaysOfMonth = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        cronMonths = WorkflowSchedule.EXPR_SPECIAL_ALL; // "*";
        cronDaysOfWeek = WorkflowSchedule.EXPR_SPECIAL_ALL; // "*";
        
        // Daily: Weekdays
      } else if ( workflowSchedule.getTriggerStyle() == WorkflowSchedule.DAILY_STYLE_WEEKDAYS ) {
        cronDaysOfMonth = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        cronMonths = WorkflowSchedule.EXPR_SPECIAL_ALL; // "*";
        cronDaysOfWeek = "MON-FRI";
        
        // Daily: Every N Days
      } else if ( workflowSchedule.getTriggerStyle() == WorkflowSchedule.DAILY_STYLE_INTERVAL ) {
        ////  Every N days , e.g. every 3 days = 0/3
        //cronDaysOfMonth = startDay + WorkflowSchedule.EXPR_SPECIAL_INCREMENT + workflowSchedule.getInterval();
        //cronMonths = WorkflowSchedule.EXPR_SPECIAL_ALL; // "*";
        //cronDaysOfWeek = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        ////throw new ParseException ( "Daily Trigger Style Currently Not Supported", workflowSchedule.getTriggerStyle() );
        
        // Since the CronTrigger seems not possible for doing "Every N days"
        // Use the SimpleTrigger instead
        SimpleTrigger trigger = new SimpleTrigger(GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + workflowSchedule.getID(),
            GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + "GROUP");
        
        if ( !Utility.isEmpty(workflowSchedule.getStartDate()) ) {
          trigger.setStartTime(workflowSchedule.getStartDate());
        }
        if ( !Utility.isEmpty(workflowSchedule.getEndDate()) ) {
          trigger.setEndTime(workflowSchedule.getEndDate());
        }
        if ( workflowSchedule.getMaxRun() <= 0 ) {
          trigger.setRepeatCount ( SimpleTrigger.REPEAT_INDEFINITELY );
        } else {
          trigger.setRepeatCount ( workflowSchedule.getMaxRun()-1 );
        }
        trigger.setRepeatInterval ( workflowSchedule.getInterval()*24L*60L*60L*1000L );
        return trigger;
        
      } else {
        //  Daily style not found
        throw new ParseException ( "Invalid Trigger Daily Style", workflowSchedule.getTriggerStyle() );
      }
      
      // Schedule Type: Weekly
    } else if ( workflowSchedule.getTriggerType() == WorkflowSchedule.TRIGGER_TYPE_WEEKLY ) {
      //// Every N weeks,  e.g. every 3 weeks = 0/3*7
      ////cronDaysOfMonth = "0" + WorkflowSchedule.EXPR_SPECIAL_INCREMENT + (workflowSchedule.getInterval() * 7);
      //cronDaysOfMonth = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
      
      //if (!Utility.isEmpty(workflowSchedule.getDaysOfWeek())) {
      //  //  e.g. SUN MON TUE WED THU FRI SAT = 1,2,3,4,5,6,7
      //  cronDaysOfWeek = joinArray(workflowSchedule.getDaysOfWeek(), WorkflowSchedule.EXPR_SPECIAL_ADDITIONAL);
      //} else {
      //  cronDaysOfWeek = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
      //}
      //cronDaysOfWeek += WorkflowSchedule.EXPR_SPECIAL_INCREMENT + (workflowSchedule.getInterval() * 7);
      
      //cronMonths = WorkflowSchedule.EXPR_SPECIAL_ALL; // "*";
      
      // Since the CronTrigger seems not possible for doing "Every N weeks"
      // Use the SimpleTrigger instead
      SimpleTrigger trigger = new SimpleTrigger(GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + workflowSchedule.getID(),
          GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + "GROUP");
      
      if ( !Utility.isEmpty(workflowSchedule.getStartDate()) ) {
        trigger.setStartTime( getNextWeeklyDate(workflowSchedule.getStartDate(), Integer.parseInt(workflowSchedule.getDaysOfWeek()[0]) ) );
        log.debug ( "Trigger Start Time = "+trigger.getStartTime() );
      }
      if ( !Utility.isEmpty(workflowSchedule.getEndDate()) ) {
        trigger.setEndTime(workflowSchedule.getEndDate());
      }
      if ( workflowSchedule.getMaxRun() <= 0 ) {
        trigger.setRepeatCount ( SimpleTrigger.REPEAT_INDEFINITELY );
      } else {
        trigger.setRepeatCount ( workflowSchedule.getMaxRun()-1 );
      }
      trigger.setRepeatInterval ( workflowSchedule.getInterval()*7L*24L*60L*60L*1000L );
      return trigger;
      
      
      // Schedule Type: Monthly
    } else if ( workflowSchedule.getTriggerType() == WorkflowSchedule.TRIGGER_TYPE_MONTHLY ) {
      // Monthly: Day Of Month
      if ( workflowSchedule.getTriggerStyle() == WorkflowSchedule.MONHTLY_STYLE_DAY_OF_MONTH ) {
        //  At a certain day of the month, L=last day
        cronDaysOfMonth = workflowSchedule.getDayOfMonth();
        
        if ( !Utility.isEmpty(workflowSchedule.getMonthsOfYear()) ) {
          //e.g. SUN MON TUE WED THU FRI SAT = 1,2,3,4,5,6,7
          cronMonths = joinArray(workflowSchedule.getMonthsOfYear(), WorkflowSchedule.EXPR_SPECIAL_ADDITIONAL);
        } else {
          cronMonths = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        }
        
        cronDaysOfWeek = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        
        // Monthly: Week Of Month
      } else if ( workflowSchedule.getTriggerStyle() == WorkflowSchedule.MONHTLY_STYLE_WEEK_OF_MONTH ) {
        //  At a certain week of the month, 6L=last friday of the month
        cronDaysOfMonth = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        
        if ( !Utility.isEmpty(workflowSchedule.getMonthsOfYear()) ) {
          //e.g. MAY JUN JUL AUG  = 5,6,7,8
          cronMonths = joinArray(workflowSchedule.getMonthsOfYear(), WorkflowSchedule.EXPR_SPECIAL_ADDITIONAL);
        } else {
          cronMonths = WorkflowSchedule.EXPR_SPECIAL_OTHER; // "?"
        }
        
        if ( WorkflowSchedule.EXPR_SPECIAL_LAST.equals(workflowSchedule.getWeekOfMonth()) ) {
          //e.g. Last FRI = 6L
          cronDaysOfWeek = workflowSchedule.getDayOfWeek() + WorkflowSchedule.EXPR_SPECIAL_LAST;
        } else {
          //e.g. 2nd FRI = 6#2
          cronDaysOfWeek = workflowSchedule.getDayOfWeek() + WorkflowSchedule.EXPR_SPECIAL_N + workflowSchedule.getWeekOfMonth();
        }
      } else {
        //  Monthly style not found
        throw new ParseException ( "Invalid Trigger Monthly Style", workflowSchedule.getTriggerStyle() );
      }
      
      // Schedule Type: Yealy
    } else if ( workflowSchedule.getTriggerType() == WorkflowSchedule.TRIGGER_TYPE_YEARLY ) {
      //  Default to every year
      throw new ParseException ( "Invalid Trigger Type", workflowSchedule.getTriggerType() );
      
    } else {
      // Trigger type not found
      throw new ParseException ( "Invalid Trigger Type", workflowSchedule.getTriggerType() );
    }
    
    cronExpression = cronSeconds
    +" "+cronMinutes
    +" "+cronHours
    +" "+cronDaysOfMonth
    +" "+cronMonths
    +" "+cronDaysOfWeek;
    log.debug ("==================================cronExpression:"+cronExpression);
    CronTrigger trigger = new CronTrigger(GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + workflowSchedule.getID(),
        GlobalConstant.JOB_PREFIX_WORKFLOW_SCHEDULE + "GROUP");
    trigger.setCronExpression ( cronExpression );
    
    if ( !Utility.isEmpty(workflowSchedule.getStartDate()) ) {
      trigger.setStartTime(workflowSchedule.getStartDate());
    }
    if ( !Utility.isEmpty(workflowSchedule.getEndDate()) ) {
      trigger.setEndTime(workflowSchedule.getEndDate());
    }
    
    return trigger;
  }
  
  private static Timestamp getNextWeeklyDate ( Timestamp date, int dayOfWeek ) {
    java.util.Calendar cl = java.util.Calendar.getInstance();
    cl.setTime( date );
    
    while ( cl.get(java.util.Calendar.DAY_OF_WEEK) != dayOfWeek ) {
      cl.add(java.util.Calendar.DATE, 1);
    }
    
    return new java.sql.Timestamp ( cl.getTime().getTime() );
  }
}
